extern crate wasm_bindgen;
use serde_json::json;
use wasm_bindgen::prelude::*;

extern crate lazy_static;
use lazy_static::lazy_static;

mod builtin_words;
mod logic;
mod state;

use logic::*;
use rand;
use rand::seq::SliceRandom;
use serde_json::Value;
use state::{Game, Stat, State};
use std::collections::HashSet;
use std::ops::DerefMut;
use std::sync::Mutex;
use std::sync::OnceLock;

// external DOM operation functions
#[wasm_bindgen]
extern "C" {
    pub fn has_state() -> bool;
    pub fn get_state_str() -> String;
    pub fn put_state_str(str: &str);

    pub fn has_config() -> bool;
    pub fn get_config_str() -> String;
    pub fn put_config_str(str: &str);

    pub fn set_game_start();
    pub fn set_game_end();

    pub fn put_stat(win: u32, lose: u32, total_attempt: u32);
    pub fn clear_guess_table();
    pub fn add_guess_item(guess: &str, cnt: u32);

    pub fn msg(str: &str);
    pub fn set_letter_content(i: u8, j: u8, str: &str);
    pub fn set_letter_style(i: u8, j: u8, style: u8);
    pub fn set_letter_button_style(i: u8, style: u8);
}

const STYLE_DISABLED: u8 = 0;
const STYLE_DEFAULT: u8 = 1;
const STYLE_TOO_MANY: u8 = 2;
const STYLE_MISPLACED: u8 = 3;
const STYLE_CORRECT: u8 = 4;

// convert letter result to unsigned 8-bit integer
// Unknown: 1, Too Many: 2, Misplaced: 3, Correct: 4
fn letter_result_to_style(letter_result: LetterResult) -> u8 {
    match letter_result {
        LetterResult::Unknown => STYLE_DEFAULT,
        LetterResult::TooMany => STYLE_TOO_MANY,
        LetterResult::Misplaced => STYLE_MISPLACED,
        LetterResult::Correct => STYLE_CORRECT,
    }
}

static FINAL_LIST: OnceLock<Vec<&str>> = OnceLock::new();
static ACCEPTABLE_SET: OnceLock<HashSet<&str>> = OnceLock::new();

// application configuration, it should only be instantiated once
struct AppConfig {
    difficult_mode: bool,
    theme: String,
    symbol_mode: bool,
}
impl AppConfig {
    // create new application configuration
    fn new() -> Self {
        Self {
            difficult_mode: false,
            theme: "dark".to_string(),
            symbol_mode: false,
        }
    }
    // load from string
    fn load_from_str(&mut self, config: String) {
        if let Value::Object(map) =
            serde_json::from_str(&config).expect("ERROR: FAILED TO PARSE CONFIG JSON")
        {
            for (key, value) in map {
                match &key as &str {
                    "difficult" => {
                        if let Value::Bool(b) = value {
                            self.difficult_mode = b;
                        } else {
                            panic!("ERROR: INVALID CONFIG ITEM VALUE(KEY: difficult)");
                        }
                    }
                    "theme" => {
                        if let Value::String(str) = value {
                            self.theme = str;
                        } else {
                            panic!("ERROR: INVALID CONFIG ITEM VALUE(KEY: theme)");
                        }
                    }
                    "symbol" => {
                        if let Value::Bool(b) = value {
                            self.symbol_mode = b;
                        } else {
                            panic!("ERROR: INVALID CONFIG ITEM VALUE(KEY: symbol)");
                        }
                    }
                    _ => {
                        panic!("ERROR: UNKNOWN CONFIG ITEM KEY");
                    }
                }
            }
        } else {
            panic!("ERROR: INVALID CONFIG JSON FORMAT");
        }
    }
    // stringify game config
    fn stringify(&self) -> String {
        serde_json::to_string(&json!({
            "difficult": self.difficult_mode,
            "theme": self.theme,
            "symbol": self.symbol_mode
        }))
        .expect("ERROR: FAIL TO WRITE JSON FORMAT")
    }
    // save game config
    fn put_config(&self) {
        put_config_str(&self.stringify());
    }
    // get difficult mode flag
    fn get_difficult_mode(&self) -> bool {
        self.difficult_mode
    }
    // set difficult mode flag
    fn set_difficult_mode(&mut self, difficult_mode: bool) {
        self.difficult_mode = difficult_mode;
        self.put_config();
    }
    // get theme
    fn get_theme(&self) -> &str {
        &self.theme
    }
    // set theme
    fn set_theme(&mut self, theme: String) {
        self.theme = theme;
        self.put_config();
    }
    // get symbol mode flag
    fn get_symbol_mode(&self) -> bool {
        self.symbol_mode
    }
    // set symbol mode flag
    fn set_symbol_mode(&mut self, symbol_mode: bool) {
        self.symbol_mode = symbol_mode;
        self.put_config();
    }
}

// application state, it should only be instantiated once
struct AppState {
    config: AppConfig,
    difficult_mode: bool,
    state: State,
    stat: Stat,
    ans: String,
    guess: String,
    game: Game,
    guess_count: usize,
    alphabet_results: AlphabetResult,
    correct: Vec<(char, usize)>,
    misplaced: Vec<char>,
    in_game: bool,
}
impl AppState {
    // create new application state
    fn new() -> Self {
        Self {
            config: AppConfig::new(),
            difficult_mode: false,
            state: State::new(),
            stat: Stat::new(),
            ans: String::new(),
            guess: String::new(),
            game: Game::new(),
            guess_count: 0,
            alphabet_results: [LetterResult::Unknown; ALPHABET_SIZE],
            correct: Vec::new(),
            misplaced: Vec::new(),
            in_game: false,
        }
    }
    // display all statistics data
    fn put_all_stats(&self) {
        // display statistics
        put_stat(
            self.stat.win() as u32,
            self.stat.lose() as u32,
            self.stat.total_attempt() as u32,
        );

        // display most frequently guessed words
        clear_guess_table();
        for (guess, cnt) in self.stat.most_frequent_words() {
            add_guess_item(&guess, cnt as u32);
        }
    }
    // initialize application state
    fn init(&mut self) {
        // read config from cookie
        if has_config() {
            let config: String = get_config_str();
            self.config.load_from_str(config);
        }

        // read state and statistics from cookie
        if has_state() {
            let state_content: String = get_state_str();
            self.state = State::parse(&state_content);
            self.stat = Stat::load_state(&self.state);
        }

        self.put_all_stats();

        // initialize letter table
        for i in 0..=MAX_TRY {
            for j in 0..WORD_LENGTH {
                set_letter_style(i as u8, j as u8, STYLE_DISABLED);
            }
        }
    }
    // get difficult mode flag
    fn get_difficult_mode(&self) -> bool {
        self.config.get_difficult_mode()
    }
    // set difficult mode flag
    fn set_difficult_mode(&mut self, difficult_mode: bool) {
        self.config.set_difficult_mode(difficult_mode);
    }
    // get theme
    fn get_theme(&self) -> &str {
        self.config.get_theme()
    }
    // set theme
    fn set_theme(&mut self, theme: String) {
        self.config.set_theme(theme)
    }
    // get symbol mode flag
    fn get_symbol_mode(&self) -> bool {
        self.config.get_symbol_mode()
    }
    // set symbol mode flag
    fn set_symbol_mode(&mut self, symbol_mode: bool) {
        self.config.set_symbol_mode(symbol_mode);
    }
    // create a new game
    fn new_game(&mut self) {
        if !self.in_game {
            // set game start in DOM
            set_game_start();

            // initialize letter table
            for i in 0..=MAX_TRY {
                for j in 0..WORD_LENGTH {
                    set_letter_content(i as u8, j as u8, "");
                    set_letter_style(i as u8, j as u8, STYLE_DISABLED);
                }
            }
            for j in 0..WORD_LENGTH {
                set_letter_style(0, j as u8, STYLE_DEFAULT);
            }

            // randomly generate answer word
            self.ans = FINAL_LIST
                .get()
                .expect("ERROR: FAIL TO GET FINAL LIST")
                .choose(&mut rand::thread_rng())
                .expect("ERROR: FAIL TO GENERATE ANSWER WORD")
                .to_ascii_uppercase();

            // load configuration and initialize game state
            self.difficult_mode = self.config.get_difficult_mode();
            self.guess = String::new();
            self.game = Game::from_answer(self.ans.clone());
            self.guess_count = 0;
            self.alphabet_results = [LetterResult::Unknown; ALPHABET_SIZE];
            self.correct = Vec::new();
            self.misplaced = Vec::new();
            self.in_game = true;
        }
    }
    // handle character key pressed event
    fn press_char(&mut self, val: u8) {
        if self.in_game && self.guess.len() < WORD_LENGTH {
            set_letter_content(
                self.guess_count as u8,
                self.guess.len() as u8,
                &String::from_utf8(vec![val + ALPHABET_START])
                    .expect("ERROR: UNABLE TO CONVERT CHAR TO STRING"),
            );
            self.guess.push((val + ALPHABET_INPUT_START) as char);
        }
    }
    // handle backspace key pressed event
    fn press_backspace(&mut self) {
        if self.in_game && !self.guess.is_empty() {
            self.guess.pop();
            set_letter_content(self.guess_count as u8, self.guess.len() as u8, "");
        }
    }
    // end current game
    fn end_game(&mut self) {
        // save game state to cookie
        set_game_end();
        self.in_game = false;
        self.state.add_game(self.game.clone());
        put_state_str(&self.state.stringify());

        // display statistics
        self.put_all_stats();
    }
    // add a guess to current game
    fn add_guess(&mut self) {
        self.guess = self.guess.to_ascii_uppercase();
        self.game.add_guess(self.guess.clone());
        self.stat.add_guess(self.guess.clone());

        // calculate the result of guess
        let ans_data: WordData = get_letters(&self.ans);
        let guess_data: WordData = get_letters(&self.guess);
        let word_results: WordResult =
            calculate_result(&mut self.alphabet_results, &ans_data, &guess_data);

        for j in 0..WORD_LENGTH {
            set_letter_style(
                self.guess_count as u8,
                j as u8,
                letter_result_to_style(word_results[j]),
            )
        }
        for i in 0..ALPHABET_SIZE {
            set_letter_button_style(i as u8, letter_result_to_style(self.alphabet_results[i]))
        }
        self.guess_count += 1;

        if self.guess == self.ans {
            // guess is correct, show message and exit game
            self.stat.add_win(self.guess_count);
            for j in 0..WORD_LENGTH {
                let chr: u8 = self
                    .ans
                    .bytes()
                    .nth(j)
                    .expect("ERROR: UNABLE TO EXTRACT CHARS FROM ANSWER");
                set_letter_content(
                    MAX_TRY as u8,
                    j as u8,
                    &String::from_utf8(vec![chr]).expect("ERROR: UNABLE TO CONVERT CHAR TO STRING"),
                );
                set_letter_style(MAX_TRY as u8, j as u8, STYLE_CORRECT);
            }
            self.end_game();
            msg("猜对了！恭喜您！");
        } else {
            // guess count reaches maximum, show message and exit game
            if self.guess_count == MAX_TRY {
                self.stat.add_lose();
                for j in 0..WORD_LENGTH {
                    let chr: u8 = self
                        .ans
                        .bytes()
                        .nth(j)
                        .expect("ERROR: UNABLE TO EXTRACT CHARS FROM ANSWER");
                    set_letter_content(
                        MAX_TRY as u8,
                        j as u8,
                        &String::from_utf8(vec![chr])
                            .expect("ERROR: UNABLE TO CONVERT CHAR TO STRING"),
                    );
                    set_letter_style(MAX_TRY as u8, j as u8, STYLE_TOO_MANY);
                }
                self.end_game();
                msg("很遗憾，您已用完全部6次猜测机会！");
            } else {
                // update difficult mode requirements
                if self.difficult_mode {
                    update_difficult_mode(
                        &self.guess,
                        &word_results,
                        &mut self.correct,
                        &mut self.misplaced,
                    );
                }
                // continue guessing
                for j in 0..WORD_LENGTH {
                    set_letter_style(self.guess_count as u8, j as u8, STYLE_DEFAULT)
                }
            }
        }

        self.guess = String::new();
    }
    // check current guess, and add it to current game if valid
    fn check_and_add_guess(&mut self) {
        if is_valid(
            &self.guess,
            &ACCEPTABLE_SET
                .get()
                .expect("ERROR: FAIL TO GET ACCEPTABLE SET"),
        ) {
            if !self.difficult_mode
                || check_difficult_mode(
                    &self.guess.to_ascii_uppercase(),
                    &self.correct,
                    &self.misplaced,
                )
            {
                self.add_guess();
            } else {
                msg("猜测单词不满足困难模式要求，请重新猜测！");
            }
        } else {
            msg("猜测单词不在词典内，请重新猜测！");
        }
    }
    // handle enter key pressed event
    fn press_enter(&mut self) {
        if self.in_game {
            self.check_and_add_guess();
        }
    }
}

lazy_static! {
    static ref APP_STATE: Mutex<AppState> = Mutex::new(AppState::new());
}

// initialize game
#[wasm_bindgen]
pub fn app_init() {
    // initialize application state
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();
    app_state.init();

    // initialize final word list
    FINAL_LIST
        .set(builtin_words::FINAL.to_vec())
        .expect("ERROR: UNABLE TO BUILD FINAL LIST");

    // initialize acceptable word set
    let acceptable_list: Vec<&str> = builtin_words::ACCEPTABLE.to_vec();
    ACCEPTABLE_SET
        .set(build_set_from_list(&acceptable_list))
        .expect("ERROR: UNABLE TO BUILD ACCEPTABLE SET");
}

// get difficult mode flag
#[wasm_bindgen]
pub fn get_difficult_mode() -> bool {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.get_difficult_mode()
}

// set difficult mode flag
#[wasm_bindgen]
pub fn set_difficult_mode(difficult_mode: bool) {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.set_difficult_mode(difficult_mode);
}

// get theme
#[wasm_bindgen]
pub fn get_theme() -> String {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.get_theme().to_string()
}

// set theme
#[wasm_bindgen]
pub fn set_theme(theme: String) {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.set_theme(theme);
}

// get symbol mode flag
#[wasm_bindgen]
pub fn get_symbol_mode() -> bool {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.get_symbol_mode()
}

// set symbol mode flag
#[wasm_bindgen]
pub fn set_symbol_mode(symbol_mode: bool) {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.set_symbol_mode(symbol_mode);
}

// create a new game
#[wasm_bindgen]
pub fn new_game() {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.new_game();
}

// handle enter key pressed event
#[wasm_bindgen]
pub fn press_enter() {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.press_enter();
}

// handle backspace key pressed event
#[wasm_bindgen]
pub fn press_backspace() {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.press_backspace();
}

// handle character key pressed event
#[wasm_bindgen]
pub fn press_char(val: u8) {
    let mut mutex_guard: std::sync::MutexGuard<'_, AppState> = APP_STATE
        .lock()
        .expect("ERROR: APP STATE MUTEX IS POISONED");
    let app_state: &mut AppState = mutex_guard.deref_mut();

    app_state.press_char(val);
}
